package it.unibo.refolding.pico.transformers;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.LongBuffer;

import com.google.common.base.Preconditions;

public class ByteArray2ShortArrayStrategyLong implements Transformer {
  private final ByteOrder byteOrder;
  private final double encoderScalingOffset;
  private final double encoderScalingMultiplier;
  private final boolean signed;
  private final MathContext mc;
  private final BigDecimal bigEncoderScalingOffset;
  private final BigDecimal bigEncoderScalingMultiplier;

  public ByteArray2ShortArrayStrategyLong(ByteOrder byteOrder, double encoderScalingOffset,
    double encoderScalingMultiplier, boolean signed) {
    this.byteOrder = byteOrder;
    this.encoderScalingOffset = encoderScalingOffset;
    this.encoderScalingMultiplier = encoderScalingMultiplier;
    this.signed = signed;
    if (!signed) {
      mc = new MathContext(64);
      bigEncoderScalingOffset = BigDecimal.valueOf(encoderScalingOffset);
      bigEncoderScalingMultiplier = BigDecimal.valueOf(encoderScalingMultiplier);
    } else {
      mc = null;
      bigEncoderScalingOffset = null;
      bigEncoderScalingMultiplier = null;
    }
  }

  @Override
  public short[] transform(byte[] input, double factor) {
    Preconditions.checkArgument((input.length & 7) == 0);
    ByteBuffer bb = ByteBuffer.wrap(input);
    bb.order(byteOrder);
    LongBuffer sb = bb.asLongBuffer();
    int len = input.length >> 3;
    short[] s = new short[len];
    // Created on first use.
    BigDecimal bigFactor = null;
    for (int i = 0; i < len; ++i) {
      long a = sb.get();
      final double d;
      if (signed | a >= 0) {
        d = (encoderScalingOffset + a * encoderScalingMultiplier) / factor;
      } else {
        BigInteger bigA = BigInteger.valueOf(a & Long.MAX_VALUE);
        bigA = bigA.setBit(63);
        BigDecimal x = new BigDecimal(bigA, mc);
        if (bigFactor == null) {
          bigFactor = BigDecimal.valueOf(factor);
        }
        BigDecimal y = bigEncoderScalingOffset.add(x.multiply(bigEncoderScalingMultiplier, mc), mc).divide(bigFactor, mc);
        d = y.doubleValue();
      }
      Preconditions.checkArgument(Math.abs(d) <= Short.MAX_VALUE, d);
      s[i] = (short) d;
    }
    assert !sb.hasRemaining();
    return s;
  }
}
